﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Library.Collections.Specialized;
using Library.Linq;
using INDEX = System.UInt64;

namespace Library.Collections.Generic
{
    [DebuggerDisplay("Count={Count}")]
    public sealed class List<T> : IList<T>, ICollectionBulk<T>, ICopyToArray<T>, IFirst<T>, IElementAt<T>, ILast<T>, IQuantifiable<T>
    {
        struct Enumerator : IEnumerator<T>, IDisposable
        {
            List<T> list;
            long next;
            INDEX version;

            T current;

            public Enumerator(List<T> list)
                : this()
            {
                this.list = list;
                version = list.version;
            }

            public void Dispose()
            {
            }

            public bool MoveNext()
            {
                if (version != list.version)
                    throw new InvalidOperationException();

                if (next < 0)
                    return false;

                if ((INDEX)next < list.count)
                {
                    current = list.data[next++];
                    return true;
                }

                next = -1;
                return false;
            }

            public T Current
            {
                get { return current; }
            }

            public void Reset()
            {
                if (version != list.version)
                    throw new InvalidOperationException();

                next = 0;
            }

            object System.Collections.IEnumerator.Current
            {
                get
                {
                    return current;
                }
            }
        }
        struct ReverseEnumerator : IEnumerator<T>, IDisposable
        {
            List<T> list;
            long next;
            INDEX version;

            T current;

            public ReverseEnumerator(List<T> list)
                : this()
            {
                this.list = list;
                version = list.version;
                next = (long)list.count - 1;
            }

            public void Dispose()
            {
            }

            public bool MoveNext()
            {
                if (version != list.version)
                    throw new InvalidOperationException();

                if (next < 0)
                    return false;

                if (next >= 0)
                {
                    current = list.data[next--];
                    return true;
                }

                next = -1;
                return false;
            }

            public T Current
            {
                get { return current; }
            }

            public void Reset()
            {
                if (version != list.version)
                    throw new InvalidOperationException();

                next = (long)list.count - 1;
            }

            object System.Collections.IEnumerator.Current
            {
                get
                {
                    return current;
                }
            }
        }

        public List()
        {
            data = new T[4];
            count = 0;
            version = 0;
        }
        public List(INDEX capacity)
        {
            data = new T[capacity];
            count = 0;
            version = 0;
        }
        public List(IEnumerable<T> enumerable)
        {
            if (enumerable == null) throw new ArgumentNullException("enumerable");

            ICountable countable = enumerable as ICountable;
            if (countable != null)
            {
                data = new T[countable.Count];
            }
            else
            {
                data = new T[0];
            }
            foreach (var item in enumerable)
                Add(item);
        }
        private List(T[] data, INDEX count)
        {
            this.data = data;
            this.count = count;
            this.version = 0;
        }

        T[] data;
        INDEX count;
        INDEX version;

        public bool Add(T item, out INDEX index)
        {
            if (count == (INDEX)data.Length)
                if (count + 1 > (INDEX)data.Length)
                    Array.Resize(ref data, (int)Math.Max(Math.Max((INDEX)data.Length * 2, 4), count + 1));
            data[count] = item;
            version++;
            index = count++;
            return true;
        }

        public bool Insert(INDEX index, T item)
        {
            if (index > count) return false;
            if (count == (INDEX)data.Length)
                if (count + 1 > (INDEX)data.Length)
                    Array.Resize(ref data, (int)Math.Max(Math.Max((INDEX)data.Length * 2, 4), count + 1));
            Array.Copy(data, (int)index, data, (int)(index + 1), (int)(count - index));
            count++;
            data[index] = item;
            version++;
            return true;
        }

        public bool RemoveAt(INDEX index)
        {
            if (index >= count) return false;
            this.count--;
            if (index < count)
            {
                Array.Copy(data, (int)(index + 1), data, (int)index, (int)(count - index));
            }
            data[count] = default(T);
            version++;
            return true;
        }

        public T this[INDEX key]
        {
            get
            {
                if (key < count)
                    return data[key];
                else
                    throw new IndexOutOfRangeException();
            }
            set
            {
                if (key < count)
                    data[key] = value;
                else
                    throw new IndexOutOfRangeException();
            }
        }

        public bool Contains(T item, out INDEX index)
        {
            int n = Array.IndexOf(data, item, 0, (int)count);
            if (n < 0)
            {
                index = 0;
                return false;
            }
            index = (INDEX)n;
            return true;
        }

        public bool Contains(T item, out INDEX index, Direction direction)
        {
            if (direction == Direction.Forward)
            {
                int n = Array.IndexOf(data, item, 0, (int)count);
                if (n < 0)
                {
                    index = 0;
                    return false;
                }
                index = (INDEX)n;
                return true;
            }
            else
            {
                int n = Array.LastIndexOf(data, item, 0, (int)count);
                if (n < 0)
                {
                    index = 0;
                    return false;
                }
                index = (INDEX)n;
                return true;
            }
        }

        public bool Contains(T item, INDEX startIndex, INDEX count, out INDEX index)
        {
            int n = Array.IndexOf(data, item, (int)startIndex, (int)count);
            if (n < 0)
            {
                index = 0;
                return false;
            }
            index = (INDEX)n;
            return true;
        }

        public bool Contains(T item)
        {
            int n = Array.IndexOf(data, item, 0, (int)count);
            if (n < 0)
            {
                return false;
            }
            return true;
        }

        public INDEX Count
        {
            get { return count; }
        }

        public INDEX CopyTo(INDEX index, T[] array, INDEX arrayIndex)
        {
            INDEX n = Math.Min((INDEX)data.Length - index, (INDEX)array.Length - arrayIndex);
            Array.Copy(data, (int)index, array, (int)arrayIndex, (int)n);
            return n;
        }

        public INDEX CopyTo(INDEX index, T[] array, INDEX arrayIndex, INDEX count)
        {
            INDEX n = Math.Min(Math.Min(count, this.count - index), (INDEX)array.Length - arrayIndex);
            Array.Copy(data, (int)index, array, (int)arrayIndex, (int)n);
            return n;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new Enumerator(this);
        }

        public IEnumerator<T> GetEnumerator(Direction direction)
        {
            if (direction == Direction.Forward)
                return new Enumerator(this);
            else
                return new ReverseEnumerator(this);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new Enumerator(this);
        }

        System.Collections.IEnumerator IEnumerableDirection.GetEnumerator(Direction direction)
        {
            if (direction == Direction.Forward)
                return new Enumerator(this);
            else
                return new ReverseEnumerator(this);
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Add(T item)
        {
            if (count == (INDEX)data.Length)
                if (count + 1 > (INDEX)data.Length)
                    Array.Resize(ref data, (int)Math.Max(Math.Max((INDEX)data.Length * 2, 4), count + 1));
            data[count++] = item;
            version++;
            return true;
        }

        public bool Remove(T item)
        {
            INDEX index;
            return Contains(item, out index) ? RemoveAt(index) : false;
        }

        public bool Remove(T item, Direction direction)
        {
            INDEX index;
            return Contains(item, out index, direction) ? RemoveAt(index) : false;
        }

        public bool Clear()
        {
            Array.Clear(data, 0, data.Length);
            count = 0;
            version++;
            return true;
        }

        public bool IsFixedSize
        {
            get { return false; }
        }

        public bool AddAll(IEnumerable<T> items)
        {
            throw new NotImplementedException();
        }

        public bool RemoveAll(IEnumerable<T> items)
        {
            throw new NotImplementedException();
        }

        public bool AddAny(IEnumerable<T> items)
        {
            throw new NotImplementedException();
        }

        public bool RemoveAny(IEnumerable<T> items)
        {
            throw new NotImplementedException();
        }

        public bool ContainsAll(IEnumerable<T> items)
        {
            throw new NotImplementedException();
        }

        public bool ContainsAny(IEnumerable<T> items)
        {
            throw new NotImplementedException();
        }

        public bool ContainsNone(IEnumerable<T> items)
        {
            throw new NotImplementedException();
        }

        bool IFirst<T>.First(out T item)
        {
            if (count == 0)
            {
                item = default(T);
                return false;
            }
            item = data[0];
            return true;
        }

        bool IElementAt<T>.ElementAt(INDEX index, out T item)
        {
            if (count == 0 || index >= count)
            {
                item = default(T);
                return false;
            }
            item = data[index];
            return true;
        }

        bool ILast<T>.Last(out T item)
        {
            if (count == 0)
            {
                item = default(T);
                return false;
            }
            item = data[count - 1];
            return true;
        }

        public bool ForAll(Func<T, bool> predicate)
        {
            for (INDEX index = 0; index < count; index++)
                if (!predicate(data[index])) return false;

            return true;
        }

        public bool Exists()
        {
            return this.count > 0;
        }

        public bool Exists(Func<T, bool> predicate)
        {
            for (INDEX index = 0; index < count; index++)
                if (predicate(data[index])) return true;

            return false;
        }

        public IList<T> Clone(Direction direction, Access access)
        {
            if (access == Access.Read)
            {
                if (direction == Direction.Forward)
                {
                    T[] _data = new T[count];
                    Array.Copy(data, _data, (int)count);
                    return new ReadOnly.Array<T>(_data);
                }
                else
                {
                    T[] _data = new T[count];
                    for (ulong i = 0; i < count; ++i)
                    {
                        _data[i] = data[count - 1 - i];
                    }
                    return new ReadOnly.Array<T>(_data);
                }
            }
            else if (access == Access.ReadWrite)
            {
                if (direction == Direction.Forward)
                {
                    T[] _data = new T[count];
                    Array.Copy(data, _data, (int)count);
                    return new List<T>(_data, count);
                }
                else
                {
                    T[] _data = new T[count];
                    for (ulong i = 0; i < count; ++i)
                    {
                        _data[i] = data[count - 1 - i];
                    }
                    return new List<T>(_data, count);
                }
            }
            else throw new NotSupportedException();
        }

        public IList<T> Clone(Direction direction)
        {
            if (direction == Direction.Forward)
            {
                T[] _data = new T[count];
                Array.Copy(data, _data, (int)count);
                return new List<T>(_data, count);
            }
            else
            {
                T[] _data = new T[count];
                for (ulong i = 0; i < count; ++i)
                {
                    _data[i] = data[count - 1 - i];
                }
                return new List<T>(_data, count);
            }
        }

        public IList<T> Clone(Access access)
        {
            if (access == Access.Read)
            {
                T[] _data = new T[count];
                Array.Copy(data, _data, (int)count);
                return new ReadOnly.Array<T>(_data);
            }
            else if (access == Access.ReadWrite)
            {
                T[] _data = new T[count];
                Array.Copy(data, _data, (int)count);
                return new List<T>(_data, count);
            }
            else
            {
                throw new NotSupportedException();
            }
        }

        public IList<T> Clone()
        {
            T[] _data = new T[count];
            Array.Copy(data, _data, (int)count);
            return new List<T>(_data, count);
        }
    }
}
